<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module" src="../../polymer-legacy.js"></script>
<body>

  <script type="module">
    window.LifeCycleBehavior1 = {

      properties: {
        foo: {
          type: String
        }
      },

      created() {
            this.__createdList = this.__createdList || [];
            this.__createdList.push('1');
      },

      attached() {
            this.__attachedList = this.__attachedList || [];
            this.__attachedList.push('1');
      },

      attributeChanged() {
            this.__attributeChangedList = this.__attributeChangedList || [];
            this.__attributeChangedList.push('1');
      }
    };

    window.LifeCycleBehavior2 = {
      created() {
        this.__createdList = this.__createdList || [];
        this.__createdList.push('2');
      },

      attached() {
        this.__attachedList = this.__attachedList || [];
        this.__attachedList.push('2');
      }
    };

    window.LifeCycleBehavior3 = {
      created() {
        this.__createdList = this.__createdList || [];
        this.__createdList.push('3');
      }
    };

    window.LifeCycleBehavior4 = {
      created() {
        this.__createdList = this.__createdList || [];
        this.__createdList.push('4');
      }
    };

    window.BehaviorA = {
      properties: {

        label: {
          type: String,
          observer: '_labelChanged'
        },

        hasOptionsA: {
          readOnly: true,
          notify: true
        },

        overridableProperty: {
          value: false
        },

        overridablePropertyB: {
          value: false
        },

        hasBehaviorA: {
          value: true
        },

        computeADependency: {
          value: true
        },

        computeA: {
          computed: '_computeProp(computeADependency)'
        }
      },

      _simpleProperty: 'A',

      hostAttributes: {
        behavior: 'A',
        element: 'A',
        user: 'A'
      },

      listeners: {
        change: '_changeHandler'
      },

      ready: function() {
        this.__readyA = true;
      },

      _labelChanged: function(label) {
        this.__label = label;
      },

      _changeHandler: function(e) {
        this.__change = e.detail.value;
      },

      _computeProp: function(a) {
        return a;
      }
    };

    window.BehaviorB = {
      properties: {

        disabled: {
          type: Boolean,
          value: false,
          observer: '_disabledChanged'
        },

        hasOptionsB: {
          readOnly: true,
          notify: true
        },

        hasBehaviorB: {
          value: true
        },

        overridablePropertyB: {
          value: true
        },

        computeADependencyDependency: {
          value: 'hi'
        },

        computeADependency: {
          computed: '_computeProp(computeADependencyDependency)'
        }

      },

      hostAttributes: {
        behavior: 'B',
        element: 'B',
        user: 'B'
      },

      _simpleProperty: 'B',

      _disabledChanged: function(disabled) {
        this.__disabled = disabled;
      },

      ready: function() {
        this.__readyB = true;
      },

    };

    window.BehaviorC = {

      properties: {

        hasBehaviorC: {
          value: true
        }

      },

      _simpleProperty: 'C'
    };

    window.BehaviorD = {

      properties: {

        hasBehaviorD: {
          value: true
        }

      },
      _simpleProperty: 'D'

    };
  </script>

  <dom-module id="single-behavior">
    <script type="module">
      import { mixinBehaviors } from '../../lib/legacy/class.js';
      import { PolymerElement } from '../../polymer-element.js';
      customElements.define('single-behavior',
        mixinBehaviors(window.BehaviorA, PolymerElement));
    </script>
  </dom-module>

  <dom-module id="lifecycle-behavior">
    <script type="module">
      import { mixinBehaviors } from '../../lib/legacy/class.js';
      import { PolymerElement } from '../../polymer-element.js';
      customElements.define('lifecycle-behavior',
        mixinBehaviors([window.LifeCycleBehavior1,
          window.LifeCycleBehavior2], PolymerElement));
    </script>
  </dom-module>

  <dom-module id="multi-behaviors">
    <script type="module">
      import { mixinBehaviors } from '../../lib/legacy/class.js';
      import { PolymerElement } from '../../polymer-element.js';
      customElements.define('multi-behaviors',
        class extends mixinBehaviors(
          [window.BehaviorA, window.BehaviorB], PolymerElement) {

          static get properties() {
            return {
              foo: {
                type: String,
                reflectToAttribute: true,
                readOnly: true,
                observer: '_fooChanged'
              },

              overridableProperty: {
                value: true
              }
            };
          }

          constructor() {
            super();
          }

          _fooChanged(foo) {
            this.__foo = foo;
          }

          _ensureAttributes() {
            this._ensureAttribute('element', 'element');
            super._ensureAttributes();
          }
      });
    </script>
  </dom-module>

  <script type="module">
    import { mixinBehaviors } from '../../lib/legacy/class.js';
    import { Polymer } from '../../polymer-legacy.js';
    import { PolymerElement } from '../../polymer-element.js';

    customElements.define('nested-behaviors',
      class extends mixinBehaviors([window.BehaviorD, window.LifeCycleBehavior1], mixinBehaviors(
        [
          [window.BehaviorB, [window.BehaviorC, window.BehaviorB], window.BehaviorA, window.LifeCycleBehavior2],
            ], PolymerElement)) {
        });

    var base = Polymer({
      is: 'sup-element',
      behaviors: [
        window.LifeCycleBehavior1,
        window.LifeCycleBehavior2
      ],
      created: function() {
        this.__createdList.push('sup');
      }
    });

    class extended extends mixinBehaviors([window.LifeCycleBehavior3,
        window.LifeCycleBehavior4], base) {

      created() {
        super.created();
        this.__createdList.push('sub');
      }
    }

    customElements.define('extended-behaviors', extended);
  </script>

  <dom-module id="behavior-registered">
    <template>
      <div id="content"></div>
    </template>
    <script type="module">
      import { mixinBehaviors } from '../../lib/legacy/class.js';
      import { PolymerElement } from '../../polymer-element.js';

      window.registerBehavior1 ={
              registeredCount: 0,
        registered: function() {
          this._createPropertyObserver('prop', 'propChanged1');
          this._createMethodObserver('propChanged2(prop)');
          this.registeredCount++;
          this.registeredProps = [this.prop1, this.prop2, this.prop3];
          this.registeredBehaviors = this.behaviors;
        },
        prop1: true,
        ready: function() {
          this._ensureAttribute('attr', true);
        },
        propChanged1: function() {
          this.propChanged1Called = true;
        },
        propChanged2: function() {
          this.propChanged2Called = true;
        }
      };

      window.registerBehavior2 ={
        prop2: true,
        registered: function() {
          this.registeredCount++;
        }
      };

      window.registerBehavior3 ={
        prop3: true,
        registered: function() {
          this.registeredCount++;
        }
      };

      class BehaviorRegistered extends mixinBehaviors([
          window.registerBehavior1,
          window.registerBehavior2,
          window.registerBehavior3
        ], PolymerElement) {

        static get is() { return 'behavior-registered';}

        _initializeProperties() {
          super._initializeProperties();
          this.registeredCount++;
        }
      }

      customElements.define(BehaviorRegistered.is, BehaviorRegistered);

      class BehaviorRegisteredExt extends BehaviorRegistered {
        static get is() { return 'behavior-registered-ext';}
      }

      customElements.define(BehaviorRegisteredExt.is, BehaviorRegisteredExt);
    </script>
  </dom-module>

  <script type="module">
    import { mixinBehaviors } from '../../lib/legacy/class.js';
    import { PolymerElement } from '../../polymer-element.js';

    var klass = mixinBehaviors([
      {
        __fooChangedCalled: 0,
        beforeRegister: function() {
          this.constructor.generatedFrom.observers = [
            '_fooChanged(foo)'
          ];
        },
        _fooChanged: function() {
          this.__fooChangedCalled++;
        }
      }
    ], PolymerElement);

    customElements.define('before-register-observers', klass);
  </script>

  <script type="module">
    import { mixinBehaviors } from '../../lib/legacy/class.js';
    import { PolymerElement } from '../../polymer-element.js';

    const Base = mixinBehaviors([{
      registered() {
        this.usedExtendedProto = this.canUseExtendedProto;
      }
    }], PolymerElement);

    customElements.define('registered-proto', mixinBehaviors([
      {canUseExtendedProto: true}
    ], Base));

  </script>

  <script type="module">
    import { mixinBehaviors } from '../../lib/legacy/class.js';
    import { PolymerElement } from '../../polymer-element.js';


    class Base extends mixinBehaviors([{
        properties: {b1: String} }], PolymerElement) {

      static get properties() {
        return { e1: String};
      }
    }

    class El extends mixinBehaviors([{
        properties: {b2: String}}], Base) {

      static get properties() {
        return { e2: String};
      }

    }

    customElements.define('extended-observed-attributes', El);

  </script>

  <test-fixture id="single">
    <template>
      <single-behavior></single-behavior>
    </template>
  </test-fixture>

  <test-fixture id="lifecycle">
    <template>
      <lifecycle-behavior foo="foo"></lifecycle-behavior>
    </template>
  </test-fixture>

  <test-fixture id="multi">
    <template>
      <multi-behaviors user="user"></multi-behaviors>
    </template>
  </test-fixture>

  <test-fixture id="nested">
    <template>
      <nested-behaviors foo="foo"></nested-behaviors>
    </template>
  </test-fixture>

  <test-fixture id="extended-behaviors">
    <template>
      <extended-behaviors></extended-behaviors>
    </template>
  </test-fixture>

  <test-fixture id="registered">
    <template>
      <behavior-registered></behavior-registered>
    </template>
  </test-fixture>

  <test-fixture id="before-register-observers">
    <template>
      <before-register-observers></before-register-observers>
    </template>
  </test-fixture>

  <test-fixture id="registered-ext">
    <template>
      <behavior-registered-ext></behavior-registered-ext>
    </template>
  </test-fixture>

  <test-fixture id="registered-proto">
    <template>
      <registered-proto></registered-proto>
    </template>
  </test-fixture>

  <test-fixture id="extended-observed-attributes">
    <template>
      <extended-observed-attributes b1="b1" e1="e1" b2="b2" e2="e2"></extended-observed-attributes>
    </template>
  </test-fixture>


<script type="module">
import { Polymer } from '../../polymer-legacy.js';
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';

suite('single behavior element', function() {
  var el;

  setup(function() {
    el = fixture('single');
  });

  test('ready from behavior', function() {
    assert.equal(el.__readyA, true);
  });

  test('properties from behavior', function() {
    el.label = 'foo';
    assert.equal(el.__label, 'foo');
  });

  test('instance behaviors', function() {
    assert.equal(el.behaviors.length, 1);
  });

  test('listener from behavior', function() {
    el.fire('change', {value: 'bar'});
    assert.equal(el.__change, 'bar');
  });

  test('property info from behavior', function() {
    assert.equal(el._hasNotifyEffect('hasOptionsA'), true);
    assert.equal(el._hasReadOnlyEffect('hasOptionsA'), true);
    assert.equal(typeof el._setHasOptionsA, 'function');
  });

  test('compute property from behavior', function() {
    assert.equal(el.computeA, true);
  });

});

suite('behavior.registered/beforeRegister', function() {
  test('can install dynamic properties', function() {
    var el = fixture('registered');
    assert.ok(el.$.content);
    el.prop = 42;
    assert.isTrue(el.propChanged1Called);
    assert.isTrue(el.propChanged2Called);
    assert.isTrue(el.hasAttribute('attr'));
  });

  test('called once for each behavior with access to element prototype', function() {
    var el = fixture('registered');
    assert.equal(el.registeredCount, 4);
    assert.equal(el.registeredBehaviors.length, 3);
    assert.equal(el.registeredBehaviors, el.behaviors);
    assert.deepEqual(el.registeredProps, [true, true, true]);
  });

  test('extending element with behaviors with registered properly registers', function() {
    var el = fixture('registered-ext');
    assert.equal(el.registeredCount, 4);
    assert.equal(el.registeredBehaviors.length, 3);
    assert.equal(el.registeredBehaviors, el.behaviors);
    assert.deepEqual(el.registeredProps, [true, true, true]);
  });

  test('add observers via behavior in beforeRegister', function() {
    var el = fixture('before-register-observers');
    el.foo = 1;
    assert.equal(el.__fooChangedCalled, 1);
  });

  test('registered called on class prototype when extended', function() {
    var el = fixture('registered-proto');
    assert.isTrue(el.usedExtendedProto);
  });

});

suite('behavior lifecycle', function() {

    var el;

    setup(function() {
      el = fixture('lifecycle');
    });

    test('lifecycle', function() {
      assert.deepEqual(el.__createdList, ['1', '2'], 'created list wrong');
      assert.deepEqual(el.__attachedList, ['1', '2'], 'attached list wrong');
      assert.deepEqual(el.__attributeChangedList, ['1'],  'attributeChanged list wrong');
    });

  });

suite('multi-behaviors element', function() {
  var el;

  setup(function() {
    el = fixture('multi');
  });

  test('ready from behaviors', function() {
    assert.equal(el.__readyA, true);
    assert.equal(el.__readyB, true);
  });

  test('instance behaviors', function() {
    assert.equal(el.behaviors.length, 2);
  });

  test('properties from behaviors', function() {
    el.label = 'foo';
    assert.equal(el.__label, 'foo');
    el.disabled = true;
    assert.equal(el.__disabled, true);
  });

  test('properties from itself', function() {
    assert.isDefined(el._setFoo, 'readOnly setter not available');
    el._setFoo('bar');
    assert.equal(el.__foo, 'bar', 'observer not getting called');
    assert.equal(el.getAttribute('foo'), 'bar', 'not getting reflected');
  });

  test('listener from behaviors', function() {
    el.fire('change', {value: 'bar'});
    assert.equal(el.__change, 'bar');
  });

  test('property info from behavior A', function() {
    assert.equal(el._hasNotifyEffect('hasOptionsA'), true);
    assert.equal(el._hasReadOnlyEffect('hasOptionsA'), true);
    assert.equal(typeof el._setHasOptionsA, 'function');
  });

  test('property info from behavior B', function() {
    assert.equal(el._hasReadOnlyEffect('hasOptionsB'), true);
    assert.equal(el._hasNotifyEffect('hasOptionsB'), true);
    assert.equal(typeof el._setHasOptionsB, 'function');
  });

  test('computed property dependency can become a computed property', function() {
    assert.equal(el.computeA, 'hi');
  });

  test('multi-behavior overrides ordering', function() {
    assert(el.overridableProperty, 'Behavior property was not overridden by prototype');
    assert(el.overridablePropertyB, 'Behavior config-property was not overridden by sub-behavior');
  });

  test('hostAttributes ordering', function() {
    assert.equal(el.attributes.behavior.value, 'B', 'Behavior hostAttribute not overridden by younger behavior');
    assert.equal(el.attributes.element.value, 'element', 'Behavior hostAttribute overridden by behavior');
    assert.equal(el.attributes.user.value, 'user', 'Behavior hostAttribute overrode user attribute');
  });

  test('behavior is null generates warning', function() {
    sinon.spy(console, 'warn');
    Polymer({
      is: 'behavior-null',
      behaviors: [
        null
      ]
    });
    document.createElement('behavior-null');
    assert.equal(console.warn.callCount, 1, 'Null behaviour should generate warning');
    console.warn.restore();
  });

  test('behavior array is unique', function() {
    customElements.define('behavior-unique', mixinBehaviors(
      [window.BehaviorA, window.BehaviorA], PolymerElement));
    assert.equal(document.createElement('behavior-unique').behaviors.length, 1);
  });

  test('duplicate behaviors keep first behavior', function() {
    customElements.define('behavior-unique-last-behavior', mixinBehaviors(
      [window.BehaviorA, window.BehaviorB, window.BehaviorC, window.BehaviorA, window.BehaviorB], PolymerElement));
    var behaviors = document.createElement('behavior-unique-last-behavior').behaviors;
    assert.deepEqual(behaviors, [window.BehaviorC, window.BehaviorA, window.BehaviorB]);
  });

});

suite('nested-behaviors element', function() {
  var el;

  setup(function() {
    el = fixture('nested');
  });

  test('nested-behavior dedups', function() {
    assert.equal(el.behaviors.length, 6);
  });

  test('nested-behavior lifecycle', function() {
    assert.deepEqual(el.__createdList, ['2', '1'], 'created list wrong');
    assert.deepEqual(el.__attachedList, ['2', '1'], 'attached list wrong');
    assert.deepEqual(el.__attributeChangedList, ['1'],  'attributeChanged list wrong');
  });

  test('nested-behavior overrides ordering', function() {
    assert.ok(el.hasBehaviorA, "missing BehaviorA");
    assert.ok(el.hasBehaviorB, "missing BehaviorB");
    assert.ok(el.hasBehaviorC, "missing BehaviorC");
    assert.ok(el.hasBehaviorD, "missing BehaviorD");
    assert.equal(el._simpleProperty, 'D', 'Behavior simple property was not overridden by sub-behavior');
  });

});

suite('extended-behaviors', function() {

  var el;

  setup(function() {
    el = fixture('extended-behaviors');
  });

  test('lifecycle', function() {
    assert.deepEqual(el.__createdList, ['1', '2', 'sup', '3', '4', 'sub'], 'created list wrong');
  });

  test('observedAttributes when extended', function() {
    const el = fixture('extended-observed-attributes');
    assert.equal(el.b1, 'b1');
    assert.equal(el.e1, 'e1');
    assert.equal(el.b2, 'b2');
    assert.equal(el.e2, 'e2');
  });

});

</script>

</body>
</html>
